package com.linglan.common.config;

import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.linglan.common.api.ResultCode;
import com.linglan.common.config.userinfo.UserContext;
import com.linglan.common.config.userinfo.UserInfo;
import com.linglan.common.constants.CommonConstants;
import com.linglan.common.constants.RequestConstants;
import com.linglan.common.enums.BizCode;
import com.linglan.common.enums.ClientType;
import com.linglan.common.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

@Slf4j
@Order(value = Integer.MIN_VALUE)
public class UserInfoInterceptor implements HandlerInterceptor {
    /**
     * Interception point before the execution of a handler. Called after
     * HandlerMapping determined an appropriate handler object, but before
     * HandlerAdapter invokes the handler.
     * <p>DispatcherServlet processes a handler in an execution chain, consisting
     * of any number of interceptors, with the handler itself at the end.
     * With this method, each interceptor can decide to abort the execution chain,
     * typically sending an HTTP error or writing a custom response.
     * <p><strong>Note:</strong> special considerations apply for asynchronous
     * request processing. For more details see
     * {@link AsyncHandlerInterceptor}.
     * <p>The default implementation returns {@code true}.
     *
     * @param request  current HTTP request
     * @param response current HTTP response
     * @param handler  chosen handler to execute, for type and/or instance evaluation
     * @return {@code true} if the execution chain should proceed with the
     * next interceptor or the handler itself. Else, DispatcherServlet assumes
     * that this interceptor has already dealt with the response itself.
     * @throws Exception in case of errors
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String uuid = UUID.randomUUID().toString();
        MDC.put(CommonConstants.TRACE_ID, uuid);

        String clientIP = ServletUtil.getClientIP(request, null);
        log.info("请求ip {}", clientIP);
        if (request.getRequestURI().equalsIgnoreCase("/healthALBxxx")) {
            //用于做健康检查http请求正常即可
            return true;
        }
        if (request.getRequestURI().equalsIgnoreCase("/")) {
            //用于做健康检查http请求正常即可
            return true;
        }
        if (request.getRequestURI().startsWith("/third-api")) {
            return true;
        }
        UserInfo userInfo = new UserInfo();
        String bizParam = request.getHeader(RequestConstants.BIZ_PARAM);
        BizCode bizCode = BizCode.getByName(bizParam);
        if (bizCode == null) {
            bizCode = BizCode.CLT;
//            log.error("bizCode数据异常");
//            throw new ServiceException(ResultCode.BIZ_CODE_NULL);
        }
        userInfo.setBizCode(bizCode);
        userInfo.setUserType(bizCode.getUserType());

        String clientTypeStr = request.getHeader(RequestConstants.CLIENT_TYPE_PARAM);
        ClientType clientType = ClientType.getClientType(clientTypeStr);
        if (clientType == null) {
            clientType = ClientType.M_WEB;
//            log.error("clientType 不合规");
//            throw new ServiceException(ResultCode.CLIENT_UN_AUTHORIZED);
        }
        userInfo.setClientType(clientType);

        String token = request.getHeader(RequestConstants.TOKEN_PARAM);
        BizCode bizCodeIn = null;
        if (StringUtils.isNotBlank(token) && StpUtil.isLogin()) {
            long loginIdAsLong = StpUtil.getLoginIdAsLong();
            String loginDevice = StpUtil.getLoginDevice();
            String bizCodeStr = loginDevice.split(CommonConstants.SPLITER)[0];
            bizCodeIn = BizCode.getByName(bizCodeStr);
            if (!bizCode.equals(bizCodeIn) && !((HandlerMethod) handler).hasMethodAnnotation(SaIgnore.class)) {
                throw new ServiceException(ResultCode.BIZ_CODE_NOT_MATCH);
            } else if (!bizCode.equals(bizCodeIn) && ((HandlerMethod) handler).hasMethodAnnotation(SaIgnore.class)) {
            } else {
                userInfo.setUserId(loginIdAsLong);
            }
        }
        UserContext.setLoginUser(userInfo);

        return true;
    }

    /**
     * Callback after completion of request processing, that is, after rendering
     * the view. Will be called on any outcome of handler execution, thus allows
     * for proper resource cleanup.
     * <p>Note: Will only be called if this interceptor's {@code preHandle}
     * method has successfully completed and returned {@code true}!
     * <p>As with the {@code postHandle} method, the method will be invoked on each
     * interceptor in the chain in reverse order, so the first interceptor will be
     * the last to be invoked.
     * <p><strong>Note:</strong> special considerations apply for asynchronous
     * request processing. For more details see
     * {@link AsyncHandlerInterceptor}.
     * <p>The default implementation is empty.
     *
     * @param request  current HTTP request
     * @param response current HTTP response
     * @param handler  the handler (or {@link HandlerMethod}) that started asynchronous
     *                 execution, for type and/or instance examination
     * @param ex       any exception thrown on handler execution, if any; this does not
     *                 include exceptions that have been handled through an exception resolver
     * @throws Exception in case of errors
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserContext.remove();
        MDC.remove(CommonConstants.TRACE_ID);
    }
}
